// $Id: PlayerDB.cpp 128 2007-11-17 15:49:11Z bram $


// TODO: (Wim) de spelernamen worden in de lowercase variant opgeslagen, in
// plaats van de originele namen
// TODO: (Bram) highscores af laten hangen van het aantal victories


/**
 * PlayerDB class definition.
 *
 * @file PlayerDB.cpp
 * @author Bram Bonn&eacute;
 * @author Wim Leers
 */


#include <stdlib.h>
#include <cstdlib>
#include <fstream>
using namespace std;
#include "PlayerDB.h"
#include "InternalException.h"


//----------------------------------------------------------------------------
// Constants.

const string PlayerDB::FILENAME = "player.rdb"; // "rdb" voor Reversi DataBase


//----------------------------------------------------------------------------
// Deconstructor.

PlayerDB::~PlayerDB(void) {
	// Sla de PlayerDB op.
	Save();

	// Vernietig nu alle Player objecten.
	map<string, Player *>::iterator it;
	for (it = m_db.begin(); it != m_db.end(); it++)
		delete it->second;
}


//----------------------------------------------------------------------------
// Public methods.

/**
 * Vraag een speler object op dmv de naam.
 *
 * @param name
 *   De naam van
 * @return
 *   Pointer naar het speler object.
 *
 * @author Wim Leers
 */
Player * PlayerDB::GetPlayer(string name) {
	// Converteer naam naar lowercase
	string lowerName = name;
	for (unsigned int i = 0; i < name.length(); i++)
		lowerName[i] = tolower(name[i]);

	// Waarom deze lelijke constructie? Omdat er anders automagisch een nieuw
	// element aan de map wordt toegevoegd, met ipv een Player object, een
	// NULL pointer!
	if (m_db.count(lowerName))
		return m_db[lowerName];
	else
		return NULL;
}

/**
 * Voeg een speler toe aan de DB.
 *
 * @param [in] player
 *   Pointer naar de speler die moet worden toegevoegd.
 *
 * @author Wim Leers
 */
void PlayerDB::AddPlayer(Player * player) {
	if (player == NULL)
		throw(InternalException(
			"PlayerDB::AddPlayer()",
			"Er werd een NULL pointer i.p.v. een geldig Player object meegegeven."
		));

	// Converteer naam naar lowercase
	string lowerName = player->GetName();
	for (unsigned int i = 0; i < lowerName.length(); i++)
		lowerName[i] = tolower(lowerName[i]);

	// Controleer eerst of deze naam nog niet bestaat.
	if (!m_db.count(lowerName))
		m_db.insert(pair<string, Player *>(lowerName, player));
}

/**
 * Wis de database van spelers.
 *
 * @author Wim Leers
 */
void PlayerDB::Erase(void) {
	// Wis de DB.
	m_db.erase(m_db.begin(), m_db.end());

	// Wis de tabelvoorstelling van het bestand in het geheugen.
	m_table.erase(m_table.begin(), m_table.end());

	// Wis het bestand.
	m_fileIO.Erase();
}

/**
 * Synchroniseert eerst de spelers DB met de tabelvoorstelling van het bestand
 * en slaat vervolgens het bestand op.
 *
 * @author Wim Leers
 */
void PlayerDB::Save(void) {
	// Wis de tabelvoorstelling van het bestand in het geheugen.
	m_table.erase(m_table.begin(), m_table.end());

	// Voeg iedere speler uit de DB toe aan de tabel.
	map<string, Player *>::iterator it;
	FILEIO_ROW row;
	for (it = m_db.begin(); it != m_db.end(); it++) {
		Player * p = it->second;
		char tmp[100];
		row.erase(row.begin(), row.end());

		row.push_back(p->GetName());
		sprintf(tmp, "%d", p->GetPlays());
		row.push_back(tmp);
		sprintf(tmp, "%d", p->GetVictories());
		row.push_back(tmp);
		m_table.push_back(row);
	}

	// Sla dit bestand op.
	m_fileIO.SetTable(m_table);
	m_fileIO.Save();
}

/**
 * Haalt de hoogste scores op en stopt deze in een string
 *
 * @return output
 *   De highscores in stringvorm.
 *
 * @author Bram Bonn&eacute;
 */
string PlayerDB::GetHighScores(void) {
	map<string, Player *, playerNameCompare>::iterator it;
	Player topfive[5];
	Player* current;
	string output = "";
	int i, j;

	for (i = 0; i < 5; i ++) {
		topfive[i].IncreasePlays(); //Zodat we nooit door nul delen
	}
	for (it = m_db.begin(); it != m_db.end(); it ++) {
		current = it->second; //Om het echte spelerobject uit de database te hebben
		if (current != NULL && current->GetPercent() >= topfive[4].GetPercent()) {
			i = 0;
			while (i < 4 && current->GetPercent() < topfive[i].GetPercent()) //i net achter de laatste met een hoger procent plaatsen
				i ++;
			//i ook nog verhogen todat hij achter de personen met een hoger winstaantal staat
			while (i < 4 && current->GetPercent() == topfive[i].GetPercent() && current->GetVictories() < topfive[i].GetVictories())
				i ++;
			for (j = 4; j > i; j --) //Alle lagere scores naar beneden shiften.
				topfive[j] = topfive[j - 1];
			topfive[i] = *current;
		}
	}
	for (i = 0; i < 5 && topfive[i].GetName() != "anonymous"; i ++) {
		char highscores[500];

		sprintf(highscores, "%d.\t%s\t\t%d%%\t(%d/%d)\n", i+1, topfive[i].GetName().c_str(), (int) (100 * topfive[i].GetPercent()), topfive[i].GetVictories(), topfive[i].GetPlays());
		output += highscores;
	}
	return output;
}


//----------------------------------------------------------------------------
// Private methods.

/**
 * Laadt de spelers uit de tabelvoorstelling van het bestand in de spelers DB.
 *
 * @author Wim Leers
 */
void PlayerDB::Load(void) {
	// Open het bestand en vraag de tabel op.
	m_fileIO.SetFileName(FILENAME);
	m_fileIO.Open();
	m_table = m_fileIO.GetTable();

	// Zorg dat de DB leeg is om mee te beginnen.
	if (m_db.size())
		m_db.erase(m_db.begin(), m_db.end());

	// Lees nu iedere rij in, alloceer telkens een Player object, importeer
	// dan telkens de gevens voor dat Player object, en voeg ten slotte dit
	// Player object toe aan de DB.
	FILEIO_TABLE::iterator itTable;
	if (m_table.size()) {
		for (itTable = m_table.begin(); itTable != m_table.end(); itTable++) {
			FILEIO_ROW::iterator itRow;
			itRow = itTable->begin();

			// Lees alle velden van het geserializede object in.
			string name = *itRow; // Kolom 1: naam
			itRow++;
			string plays = *itRow; // Kolom 2: aantal spelletjes gespeeld
			itRow++;
			string victories = *itRow; // Kolom 3: aantal overwinningen
			itRow++;

			// Instantieer een nieuw object dmv de ingelezen velden en voeg
			// dit toe aan de DB.
			Player * p = new Player(name, atoi(plays.c_str()), atoi(victories.c_str()));

			// Converteer naam naar lowercase
			string lowerName = p->GetName();
			for (unsigned int i = 0; i < lowerName.length(); i++)
				lowerName[i] = tolower(lowerName[i]);

			m_db.insert(pair<string, Player *>(lowerName, p));
		}
	}
}
